package com.k8.common.factroy.loader;

import com.k8.common.annotation.Auto;
import com.k8.common.annotation.SPI;
import com.k8.common.compiler.generator.AdaptiveClassCodeGenerator;
import com.k8.common.compiler.javassistCompiler.Compiler;
import com.k8.common.extension.Activate;
import com.k8.common.extension.Adaptive;
import com.k8.common.extension.Holder;
import com.k8.common.factroy.instance.InstanceStrategyFactory;
import com.k8.common.util.StrUtil;
import com.k8.rpc.url.URL;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
public class ExtensionLoader<E> {

    private final Lock loadClassLock = new ReentrantLock();
    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
    private String cachedDefaultName;
    //通过name获取实例
    private final Holder<E> adaptiveInstance = new Holder<>();
    private final static String Scan_Dir = "META-INF" + File.separator + "k8Internal";
    private final static String Jdk_Scan_Dir = "META-INF" + File.separator + "service";
    private volatile String adaptiveName;
    private final Map<String, Class<? extends E>> cacheClassesMap = new HashMap<>();
    private final List<Class<? extends E>> cacheClasses = new LinkedList<>();
    private final Map<String, List<String[]>> cacheActivateValues = new HashMap<>();
    private final Map<String, Set<String>> cacheActivateGroup = new HashMap<>();
    private final List<String> activateSortList = new ArrayList<>();
    InstanceStrategyFactory instanceStrategyFactory;

    ExtensionDirector director;
    Class<E> type;

    volatile boolean canAdaptive;
    private final Holder<Boolean> loadClassStatus = new Holder<>();
    private final static String Aop_BeanPostProcessor = "smart.instantiation.aware.bean.post.processor";


    public ExtensionLoader(Class<E> clazz, ExtensionDirector director, InstanceStrategyFactory instanceStrategyFactory) {
        this.director = director;
        this.type = clazz;
        cacheDefaultExtensionName();
        checkCanAdaptive();
        loadClassStatus.setValue(false);
        this.instanceStrategyFactory = instanceStrategyFactory;
    }

    public String getDefaultName() {
        return this.cachedDefaultName;
    }

    private void checkCanAdaptive() {
        for (Method method : this.type.getMethods()) {
            Adaptive annotation = method.getAnnotation(Adaptive.class);
            if (annotation != null) {
                for (Parameter parameter : method.getParameters()) {
                    if (parameter.getType().isAssignableFrom(URL.class)) {
                        canAdaptive = true;
                        return;
                    }
                }
            }
        }
        canAdaptive = false;
    }

    public Class<? extends E> getExtensionClass(String name) {
        loadClass();
        return cacheClassesMap.get(name);
    }

    public List<Class<? extends E>> getExtensionClasses() {
        loadClass();
        return Collections.unmodifiableList(cacheClasses);
    }

    public List<String> getAllNames() {
        loadClass();
        return Collections.unmodifiableList(activateSortList);
    }

    public Map<String, Class<? extends E>> getExtensionClassesMap() {
        loadClass();
        return Collections.unmodifiableMap(cacheClassesMap);
    }

    private void cacheDefaultExtensionName() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation == null) {
            return;
        }

        String value = defaultAnnotation.value();
        if (!(value = value.trim()).isEmpty()) {
            String[] names = NAME_SEPARATOR.split(value);
            if (names.length > 1) {
                throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }

    public E getAdaptiveExtensionToInject() {
        return getAdaptiveExtension(true, true);
    }

    public E getAdaptiveExtension() {
        return getAdaptiveExtension(false, true);
    }

    public E getAdaptiveExtension(boolean inject, boolean requireRegistry) {
        loadClass();
        if (canAdaptive) {
            E value = adaptiveInstance.getValue();
            if (value == null) {
                synchronized (adaptiveInstance) {
                    if (adaptiveInstance.getValue() == null) {
                        adaptiveInstance.setValue(adaptiveInit(inject, requireRegistry));
                    }
                }
            }
            return adaptiveInstance.getValue();
        }
        return null;
    }

    private E adaptiveInit(boolean inject, boolean registrationRequired) {
        if (adaptiveName != null && !adaptiveName.isEmpty()) {//类上标注了@Adaptive则直接获取
            if (inject) {
                return getExtensionToInject(adaptiveName);
            }
            return getExtension(adaptiveName, registrationRequired);
        } else {
            // todo verify generator可能会出错, 测试第一个包含adaptive的方法
            String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
            Compiler compiler = director.getExtensionLoader(Compiler.class).getAdaptiveExtension();
            Class<?> compile = compiler.compile(code, type.getClassLoader());
            try {
                return (E) compile.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private final static Set<String> jarHasSet;

    static {
        jarHasSet = new HashSet<>();
        String classPath = System.getProperty("java.class.path");
        String[] classPathElements = classPath.split(File.pathSeparator);

        for (String element : classPathElements) {
            File file = new File(element);
            if (file.isFile() && element.endsWith(".jar")) {
                // 处理JAR文件
                handleJarFile(file);
            } else if (file.isDirectory()) {
                // 处理目录形式的类路径（如IDE中的classes目录）
                handleDirectory(file);
            }
        }
    }

    // 处理JAR文件中的资源
    private static void handleJarFile(File jarFile) {
        try (JarFile jar = new JarFile(jarFile)) {
            jar.stream()
                    .filter(entry -> !entry.isDirectory())
                    .filter(entry -> entry.getName().startsWith(Scan_Dir) || entry.getName().startsWith(Jdk_Scan_Dir))
                    .forEach(entry -> jarHasSet.add(entry.getName()));
        } catch (IOException e) {
            throw new RuntimeException("处理JAR文件失败: " + jarFile.getAbsolutePath(), e);
        }
    }

    // 处理目录中的资源（如classes目录下的META-INF/service）
    private static void handleDirectory(File rootDir) {
        // 构建目标目录的完整路径（rootDir/META-INF/service）
        File scanDir = new File(rootDir, Scan_Dir);
        if (scanDir.exists() && scanDir.isDirectory()) {
            // 递归扫描目录下的所有文件
            scanDirectory(scanDir, Scan_Dir);
        }

        // 处理JDK相关目录（根据实际需求调整）
        File jdkScanDir = new File(rootDir, Jdk_Scan_Dir);
        if (jdkScanDir.exists() && jdkScanDir.isDirectory()) {
            scanDirectory(jdkScanDir, Jdk_Scan_Dir);
        }
    }

    private static void scanDirectory(File currentDir, String baseDirName) {
        File[] files = currentDir.listFiles();
        if (files == null) return;
        for (File file : files) {
            if (!file.isDirectory()) {
                String relativePath = baseDirName + File.separator + file.getName();
                jarHasSet.add(relativePath);
            }
        }
    }

    private void load(Class<E> type) {
        String targetResource = Scan_Dir + File.separator + type.getName();
        String jdkTargetResource = Jdk_Scan_Dir + File.separator + type.getName();
        ClassLoader classLoader = type.getClassLoader();
        if (jarHasSet.contains(targetResource)) {
            resolveMultiResources(classLoader, targetResource, false);
        } else if (jarHasSet.contains(jdkTargetResource)) {
            resolveMultiResources(classLoader, jdkTargetResource, true);
        }


    }

    private void resolveMultiResources(ClassLoader classLoader, String resourcePath, boolean isJdk) {
        try {
            Enumeration<java.net.URL> urlEnumeration = classLoader.getResources(resourcePath);
            while (urlEnumeration.hasMoreElements()) {
                java.net.URL resource = urlEnumeration.nextElement();
                resolveResource(classLoader, resource, isJdk);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void resolveResource(ClassLoader classLoader, java.net.URL resource, boolean isJdk) {
        if (resource != null) {
            try {
                Files.readAllLines(Paths.get(resource.toURI()))
                        .stream()
                        .filter(line -> {
                            return line != null && !line.isEmpty() && !line.startsWith("#");
                        }).forEach(line -> {
                            if (isJdk) {
                                resolveLine(classLoader, line, line);
                            } else {
                                String[] split = line.split("=");
                                if (split.length != 2)
                                    throw new RuntimeException("Pattern of line is error, file: " + resource.getPath() + " , line: " + line);
                                resolveLine(classLoader, split[0], split[1]);
                            }
                        });
            } catch (IOException | URISyntaxException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void resolveLine(ClassLoader classLoader, String name, String className) {
        Class<? extends E> clazz = null;
        try {
            clazz = (Class<? extends E>) classLoader.loadClass(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        putExtensionClass(clazz, name);
    }

    /*
     * 初始化数据但不实例化
     * */
    private void loadClass() {
        if (!loadClassStatus.getValue()) {
            loadClassLock.lock();
            if (!loadClassStatus.getValue()) {
                load(type);//自定义加载
                if (!activateSortList.isEmpty()) {
                    activateSortList.sort((name1, name2) -> {
                        Class<? extends E> class1 = cacheClassesMap.get(name1);
                        Class<? extends E> class2 = cacheClassesMap.get(name2);
                        Activate annotation1 = class1.getAnnotation(Activate.class);
                        Activate annotation2 = class2.getAnnotation(Activate.class);
                        if (annotation1 == null || annotation2 == null) return 1;
                        return annotation1.order() - annotation2.order();
                    });
                }
                loadClassStatus.setValue(true);
            }
            loadClassLock.unlock();
        }
    }

    private void putExtensionClass(Class<? extends E> clazz, String simpleClassName) {
        if (clazz.isAnnotationPresent(Auto.class)) return;
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            if (clazz.isAnnotationPresent(Activate.class))
                throw new RuntimeException("@Adaptive cannot exist with @Activate");
            adaptiveName = simpleClassName;
            canAdaptive = true;
        } else if (clazz.isAnnotationPresent(Activate.class)) {
            Activate annotation = clazz.getAnnotation(Activate.class);
            cacheActivateGroup.put(simpleClassName, Arrays.stream(annotation.group()).collect(Collectors.toSet()));
            String[] value = annotation.value();
            List<String[]> valuePair = new LinkedList<>();
            for (String s : value) {
                if (judgeActivateValue(s)) {
                    String[] pair = s.split(":");
                    valuePair.add(pair);
                } else {
                    log.error("error pattern @activate#value{}", value);
                }
            }
            activateSortList.add(simpleClassName);
            cacheActivateValues.put(simpleClassName, valuePair);
        }
        Class<? extends E> aClass = cacheClassesMap.get(simpleClassName);
        if (aClass != null)
            throw new RuntimeException("Name of extension is duplicate: class1: " + aClass.getName() + " class2: " + clazz.getName());
        cacheClassesMap.put(simpleClassName, clazz);
        cacheClasses.add(clazz);
    }

    private boolean judgeActivateValue(String value) {
        return null != value && value.contains(":") && (value.indexOf(":") == value.lastIndexOf(":"));
    }

    public List<E> getExtensions() {
        return getExtensions(true);
    }

    public List<E> getExtensions(boolean registrationRequired) {
        loadClass();
        List<E> result = new ArrayList<>(cacheClasses.size());
        for (String name : cacheClassesMap.keySet()) {
            result.add(getExtension(name, registrationRequired));
        }
        return result;
    }

    /**
     * 若要在adaptive中获取同接口的其它实现需要排除自己，否则会死循环
     */
    public List<E> getExtensionsExcludeAdaptive() {
        List<E> result = new ArrayList<>(cacheClasses.size());
        cacheClassesMap.forEach((name, clazz) -> {
            if (!clazz.isAnnotationPresent(Adaptive.class)) {
                result.add(getExtension(name));
            }
        });
        return result;
    }


    public E getExtension(String name, boolean registrationRequired) {
        loadClass();
        if (StrUtil.isEmpty(name)) return null;
        Class<? extends E> aClass = cacheClassesMap.get(name);
        if (aClass == null) return null;
        if (registrationRequired) {
            return instanceStrategyFactory.getOrRegistryBean(aClass, name);
        } else {
            return instanceStrategyFactory.getBean(aClass, name);
        }
    }

    public E getExtension(String name) {
        return getExtension(name, true);
    }

    public E getExtensionToInject(String name) {
        loadClass();
        Class<? extends E> aClass = cacheClassesMap.get(name);
        if (aClass == null) return null;
        //throw new BeanInstanceException("This class of extensionName is not exist, interface of spi: "+type+" name: "+name)
        return instanceStrategyFactory.getBeanOrRegistryToInject(aClass, name);
    }

    public E getDefaultExtension() {
        return getDefaultExtension(true);
    }

    public E getDefaultExtension(boolean registrationRequired) {
        if (StrUtil.isNotEmpty(this.cachedDefaultName)) {
            return getExtension(cachedDefaultName, registrationRequired);
        }
        return null;
    }

    public List<E> getActivateExtensions() {
        if (activateSortList == null) return Collections.emptyList();
        return activateSortList.stream().map(this::getExtension).toList();
    }

    public List<E> getActivateExtension(URL url, String[] values, String group) {
        loadClass();
        if (activateSortList == null) return Collections.emptyList();

        Set<String> namesSet = Arrays.stream(values).collect(Collectors.toSet());
        return activateSortList.stream()
                .filter(name -> {
                    Set<String> groups = cacheActivateGroup.get(name);
                    return group != null && !group.isEmpty() && groups.contains(group);
                })
                .filter(namesSet::contains).filter(name -> {
                    List<String[]> keyValues = cacheActivateValues.get(name);
                    for (String[] keyValue : keyValues) {
                        //若key存在与url中则匹配url与@Activate的value值是否相等
                        String paraValue = url.getParameter(keyValue[0]);
                        if (paraValue != null && !paraValue.isEmpty() && paraValue.equals(keyValue[1])) {
                            continue;
                        } else {
                            return false;
                        }
                    }
                    return true;
                }).map(this::getExtension).toList();
    }

    public E createProtoBean(String name) {
        loadClass();
        Class<? extends E> aClass = cacheClassesMap.get(name);
        if (aClass == null) return null;
        return this.instanceStrategyFactory.createProtoBean(aClass);
    }

    public E createProtoBeanOrDefault(String name) {
        loadClass();
        Class<? extends E> aClass = cacheClassesMap.get(name);
        if (aClass == null) return createProtoBean(getDefaultName());
        return this.instanceStrategyFactory.createProtoBean(aClass);
    }

    static class JarContext {
        JarFile jarFile;
        JarEntry jarEntry;

        public JarContext(JarFile jarFile, JarEntry jarEntry) {
            this.jarFile = jarFile;
            this.jarEntry = jarEntry;
        }
    }
}
